Why Theming Matters
Consistent theming is fundamental to creating professional, maintainable Flutter applications.
Benefits of Theming
- Coherent visual identity: Consistent theming gives your app a coherent visual identity and simplifies maintenance.
- Centralized styles: Centralized styles prevent duplicated styling code and make global changes straightforward.
- Accessibility support: Theming supports accessibility (contrasting colors, scalable text) and reduces visual bugs.
App-Level Theming with ThemeData
Flutter's ThemeData provides a comprehensive system for app-wide styling.
Setting Up Theming
Wrap your app with MaterialApp(theme: ThemeData(...)) and provide darkTheme and themeMode for light/dark switching.
Common ThemeData Fields
primaryColor, colorScheme, textTheme, iconTheme, buttonTheme, inputDecorationTheme, appBarTheme, floatingActionButtonTheme.
Example Pattern (Conceptual)
MaterialApp(
title: 'Afrilen App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
textTheme: TextTheme(
headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
bodyText2: TextStyle(fontSize: 14),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(shape: StadiumBorder()),
),
),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system,
home: HomePage(),
)
Guideline: Use ColorScheme and fromSeed or ColorScheme.light/dark to keep colors harmonious.
Text Styles and Typography
Consistent typography is key to professional app design.
TextTheme Usage
- Define a
TextThemein ThemeData and reference it viaTheme.of(context).textTheme. - Prefer semantic styles (headline, subtitle, body) instead of hard-coded sizes at use sites.
Usage Example
Text('Welcome', style: Theme.of(context).textTheme.headline6);
Best practice: Create a typography spec (font families, sizes, weights) and export as a centralized file for consistency.
Color Palettes and Constants
Organizing colors systematically makes theming manageable and consistent.
Color Token Organization
- Keep color tokens in one place (e.g.,
app_colors.dart) with semantic names:primary,accent,background,surface,error,textPrimary,textSecondary. - Use semantic names at the widget level, not raw hex values.
Example Token File (Conceptual)
class AppColors {
static const primary = Color(0xFF0066CC);
static const accent = Color(0xFFFFC107);
static const background = Color(0xFFF6F7FB);
}
Button, Input, and Card Theming
Theming common components ensures consistency across your app.
Component Theming
- Override
ElevatedButtonThemeData,InputDecorationTheme, andCardThemein ThemeData so components look consistent across the app. - Avoid styling buttons at every call site; style centrally and pass minimal overrides when needed.
Example: InputDecorationTheme
InputDecorationTheme(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
filled: true,
fillColor: Colors.white,
)
Dark Mode and Adaptive Theming
Supporting dark mode is essential for modern applications.
Dark Mode Setup
- Provide both
themeanddarkTheme. Let users choose withthemeMode: ThemeMode.system/User/Light/Dark. - Ensure contrast and iconography are legible in both modes; test color combinations and images.
Checklist for Dark Mode Readiness
- Test on large textScaleFactor values.
- Use semantic surface colors rather than fixed backgrounds.
- Prefer dynamic image variants or apply color filters to icons where appropriate.
Creating Reusable Custom Widgets
Building custom widgets improves code reusability and maintainability.
Best Practices
- Build custom widgets by composing existing widgets; prefer StatelessWidget for presentational widgets and StatefulWidget only when local state is necessary.
- Make widgets configurable via constructor parameters (text, colors, callbacks) and provide sensible defaults.
Pattern: Small, Focused Widget
class ProfileCard extends StatelessWidget {
final String name;
final String role;
final VoidCallback? onFollow;
const ProfileCard({
Key? key,
required this.name,
required this.role,
this.onFollow,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
CircleAvatar(child: Icon(Icons.person)),
SizedBox(width: 12),
Expanded(child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name, style: Theme.of(context).textTheme.subtitle1),
Text(role, style: Theme.of(context).textTheme.bodyText2),
],
)),
ElevatedButton(onPressed: onFollow, child: Text('Follow')),
],
),
),
);
}
}
Design note: Expose callbacks for interactivity and avoid tightly coupling widget to application services.
Theming Custom Widgets
Custom widgets should integrate seamlessly with your app's theme.
Theme Integration
Custom widgets should consume theme tokens (colors, text styles) rather than defining their own hard-coded styles. This keeps custom widgets consistent with the app theme.
Example
final titleStyle = Theme.of(context).textTheme.subtitle1?.copyWith(
color: Theme.of(context).colorScheme.primary
);
Config and Style Propagation: InheritedWidget and Provider
Understanding how to propagate configuration and styles down the widget tree is essential for scalable apps.
When to Use Each
- Use
InheritedWidgetfor lightweight, performance-friendly propagation of config down the tree. For app-wide state or theme toggles, preferProviderfor ergonomics and testability. - Example use cases: current locale, responsive breakpoints, feature flags, or custom theme overrides.
Conceptual Pattern
class AppConfig extends InheritedWidget {
final String apiBaseUrl;
const AppConfig({required this.apiBaseUrl, required Widget child}) : super(child: child);
static AppConfig of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!;
@override bool updateShouldNotify(covariant AppConfig old) => apiBaseUrl != old.apiBaseUrl;
}
Guideline: Don't overuse; prefer Provider or other state management solutions for complex scenarios.
Creating Adaptive Widgets
Adaptive widgets provide platform-appropriate experiences.
Platform Adaptation
Make widgets that adapt to platform or size: use Platform.isIOS checks sparingly, prefer Theme.of(context).platform or conditional widgets that use Material or Cupertino components as required.
Pattern: Adaptive Button
Widget adaptiveButton(BuildContext context, String label, VoidCallback onPressed) {
if (Theme.of(context).platform == TargetPlatform.iOS) {
return CupertinoButton(child: Text(label), onPressed: onPressed);
}
return ElevatedButton(onPressed: onPressed, child: Text(label));
}
Accessibility Considerations in Styling
Accessible styling ensures your app works for all users.
Accessibility Best Practices
- Ensure contrast ratio between text and background meets accessibility standards.
- Respect large font sizes by using relative sizing via
TextThemeandMediaQuery.textScaleFactor. - Provide semantic labels for interactive custom widgets and ensure tap targets meet minimum size.
Theming Strategy and File Organization
Organizing theme files properly makes maintenance easier as your app grows.
Suggested File Layout
lib/theme/app_theme.dart— central ThemeData builder and toggleslib/theme/app_colors.dart— color tokenslib/theme/typography.dart— text styles and font familieslib/widgets/— reusable custom widgets (ProfileCard, PrimaryButton)lib/config/— AppConfig or constants
Reason: Separation of concerns keeps style maintenance scalable as the app grows.
Exercises
Practice what you've learned with these exercises:
1. App theme implementation
Create AppTheme file with light and dark ThemeData. Use a seed color and customize textTheme, elevatedButtonTheme, and inputDecorationTheme. Provide a toggle that switches theme at runtime using Provider or ValueNotifier.
2. Custom PrimaryButton widget
Implement PrimaryButton that reads colors from Theme and accepts isLoading to show a CircularProgressIndicator. Make it accessible with semantic labels.
3. Themed profile card
Reuse ProfileCard from earlier, but refactor it to use theme tokens for paddings, colors, and text styles. Add a variant parameter (compact, regular) to change spacing and font sizes.
4. Dark mode audit
Build a small screen with mixed content (images, text, icons) and switch between light/dark themes. Document any issues found (contrast, unreadable icons) and fix them.
Session Assignment
Complete Exercises 1–3. Provide screenshots for light and dark themes, and include a short README describing theme tokens used, why they were chosen, and how to extend the theme for new components.